﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//动态内存管理
//经典的笔试题

//一.
#include <string.h>
#include <stdlib.h>
//void GetMemory(char* p)
//{
//	p = (char*)malloc(100);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(str);
//	strcpy(str, "hello world");
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}
//1.str传给p的时候（值传递），p是str的临时拷贝，有自己独立的空间，当GetMemory函数内部申请了空间后，地址放在p中时，str依然是NULL。当
//GetMemory函数返回之后，strcpy拷贝的时候，形成了非法访问内存
//2.在GetMemory函数内部，动态申请了内存，但是没有释放，会内存泄露
//3.添加对 malloc 返回值的检查，避免分配失败时操作空指针。
//正确做法：传递指针的地址
//void GetMemory(char** p)
//{
//	*p = (char*)malloc(100);// 传递指针的地址
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str);
//  if(str != NULL)
//		strcpy(str, "hello world");
//		printf(str);
//	//释放
//	free(str);
//	str = NULL;
//}
//int main()
//{
//	Test();
//	return 0;
//}
//或者返回动态分配的指针:
//char* GetMemory()
//{
//	char* p = (char*)malloc(100);
//	return p;
//  //return (char*)malloc(100);
//}
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();
//  if(str != NULL)
//		strcpy(str, "hello world");
//		printf(str);
//	//释放
//	free(str);
//	str = NULL;
//}
//int main()
//{
//	Test();
//	return 0;
//}

//int main()
//{
//	//只有字符串字面量（或字符数组）可以隐式转换为指针并直接传递给printf，因为C语言对字符串有特殊的语法支持。
//	// 其他数据类型需显式传递值或正确解引用指针，否则会导致类型错误或未定义行为。
//	char* p = "hehe\n";
//	printf("hehe\n");// 直接传递字符串字面量
//	printf(p);// 传递指向字符串的指针
//}
//其他数据类型（如int、float等）无法像字符串一样直接通过指针隐式传递
//无隐式指针转换：非字符串类型的字面量（如整数5、浮点数3.14）没有隐式转换为指针的规则：
//int main()
//{
//	int* p = 5;// 错误！类型不匹配（无法将int赋值给int*）
//	return 0;
//}
//需要显式解引用：即使通过变量存储值，传递指针时也需要显式解引用才能获取值：
//int main()
//{
//	int a = 5;
//	int* p = &a;
//	printf("%d", *p);  // 正确：输出5
//	printf("%d", p);    // 错误：输出指针地址（未定义行为）
//	return 0;
//}

//二.
//返回栈空间地址的问题
//char* GetMemory(void)
//{
//	char p[] = "hello world";// 局部数组，存储在栈内存
//	return p;// 函数结束后，栈内存被释放，p的地址失效
//}
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();//函数返回后，局部变量 p 的内存被系统回收，但 str 仍指向该地址，形成悬挂指针。
//	printf(str);// 可能输出乱码或崩溃
//}
//int main()
//{
//	Test();
//	return 0;
//}
//GetMemory 函数内部定义了一个局部数组 char p[] = "hello world";，并返回其指针。
// 问题：局部数组存储在栈内存中，函数结束后栈内存被释放，
// 此时返回的指针指向无效内存（悬挂指针）。后续通过 str 访问会导致未定义行为（如输出乱码或程序崩溃）。
//悬空指针：是指向已经被释放或无效内存区域的指针。这种指针虽然保留了之前的内存地址，但地址中的内容可能已被系统回收或重新分配，
// 访问它会导致 未定义行为（如程序崩溃、数据错误、输出乱码等）。

//举个例子
//int* test()
//{
//	int a = 10;
//	return &a;
//}
//void dum()
//{
//	int b = 20;// 覆盖原栈内存
//}
//int main()
//{
//	int* p = test();//此时p是悬空指针，这种指针保留了之前的内存地址，如果内存地址中的值 10 暂时未被其他数据覆盖，则会输出10
//	//printf("hehe\n");//而如果在main函数内没有立即进行打印，而是再插入另一个printf，那么之前的内存地址内容就被覆盖，会输出随机值
//	dum();//或者插入其他函数调用，可能输出20或随机值
//	printf("%d\n", *p);
//	return 0;
//}

//正确方法：
//1.返回字符串常量的指针:
//char* GetMemory(void)
//{
//	char* p = "hello world";
//	return p;// 字符串常量存储在只读区，生命周期为整个程序
//	//return "hello world";
//}
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}

//2.使用动态内存分配:
//char* GetMemory(void)
//{
//	char* p = (char*)malloc(strlen("hello world") + 1);// +1 用于容纳 '\0'
//	if (p != NULL)
//	{
//		strcpy(p, "hello world");
//	}
//	return p;
//}
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();
//	if (str != NULL)
//	{
//		printf("%s\n", str);
//	}
//	free(str);
//	str = NULL;
//}
//int main()
//{
//	Test();
//	return 0;
//}

//3.使用静态变量
//char* GetMemory(void)
//{
//	static char p[] = "hello world";// 静态变量生命周期为整个程序
//	return p;
//}
//void Test(void)
//{
//	char* str = NULL;
//	str = GetMemory();
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}

//三.
//void GetMemory(char** p, int num)
//{
//	*p = (char*)malloc(num);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str, 100);
//	strcpy(str, "hello");
//	printf(str);
//}
//int main()
//{
//	Test();
//	return 0;
//}
//malloc的返回值没有判断并且存在内存泄露
//正确方法：
//void GetMemory(char** p, int num)
//{
//	*p = (char*)malloc(num);
//}
//void Test(void)
//{
//	char* str = NULL;
//	GetMemory(&str, 100);
//	if (str == NULL)
//	{
//		perror("malloc");
//		return;
//	}
//	strcpy(str, "hello");
//	printf(str);
//	free(str);
//	str = NULL;
//}
//int main()
//{
//	Test();
//	return 0;
//}

//四.
//void Test(void)
//{
//	char* str = (char*)malloc(100);//未对malloc返回值进行判断
//	strcpy(str, "hello");
//	free(str);// 释放内存
//	if (str != NULL)// str 未被置空，条件仍为真
//	{
//		strcpy(str, "world");// 操作已释放的内存（未定义行为）
//		printf(str);// 可能崩溃或输出乱码;避免直接使用用户输入作为格式字符串,
//                                         若字符串中包含 % 符号（如 str 被篡改为 "%s"），可能引发格式字符串漏洞。
//	}
//}
//int main()
//{
//	Test();
//	return 0;
//}
//正确方法：
//void Test(void)
//{
//	char* str = (char*)malloc(100);
//	if (str == NULL)
//	{
//		perror("malloc");
//		return;
//	}
//	strcpy(str, "hello");
//	printf("%s\n", str);  // 安全输出
//	free(str);
//	str = NULL;// 置空指针，避免误用
//
//	// 后续操作需确保指针有效
//	if (str != NULL)
//	{
//		// 此处代码永远不会执行
//		strcpy(str, "world");
//		printf(str);
//	}
//}
//int main()
//{
//	Test();
//	return 0;
//}

//格式字符串漏洞 是指当程序使用用户控制的字符串作为 printf、sprintf 等函数的格式字符串参数时，
// 攻击者可能通过插入特殊格式符（如% s, % x, % n 等）读取或篡改内存数据，甚至执行恶意代码。
//printf工作机制：
//printf 的第一个参数是格式字符串（可以在这里加上自定义的文字等），用于指定输出格式，第二个参数是用户输入的字符串
//printf("str = %s", str);  // 安全用法：显式指定格式为字符串

//int main()
//{
//	char str[] = "%s";    // 用户输入的字符串包含格式符 "%s"
//	printf(str);          // 等同于 printf("%s");
//	return 0;
//}

//柔性数组
//C99中，结构中的最后⼀个元素允许是未知⼤⼩的数组，这就叫做『柔性数组』成员
//struct st_type
//{
//	int i;
//	int a[0];//柔性数组成员 
//};
////有些编译器会报错⽆法编译可以改成：
//struct st_type
//{
//	int i;
//	int a[];//数组的大小是未知的 - 柔性数组成员
//};

//柔性数组的特点
//typedef struct st_type
//{
//	//1.结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
//	int i;
//	char a[0];//柔性数组成员 
//}type_a;
//int main()
//{
//	//2.sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
//	printf("%d\n", sizeof(type_a));//输出的是4 
//	return 0;
//}
//3.包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配，并且分配的内存应该⼤于结构的⼤⼩，以适应柔性数组的预期⼤⼩。
//struct S
//{
//	int i;
//	char a[0];//柔性数组成员 
//};
//int main()
//{
//	//printf("%d\n", sizeof(struct S));//输出的是4 
//	struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(char));
//	if (ps == NULL)
//	{
//		return 1;
//	}
//	ps->i = 100;
//	printf("%d\n", ps->i);
//	int i = 0;
//	for (i = 0; i < 10; i++)
//	{
//		ps->a[i] = 'Q';
//	}
//	for (i = 0; i < 10; i++)
//	{
//		printf("%c ", ps->a[i]);//Q Q Q Q Q Q Q Q Q Q 
//	}
//  printf("\n");
//	//增容
//	struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(char));
//	if (ptr != NULL)
//	{
//		ps = ptr;
//	}
//	else
//	{
//		perror("realloc");
//		return 1;
//	}
//	for (i = 10; i < 20; i++)
//	{
//		ps->a[i] = 'A';
//	}
//	for (i = 0; i < 20; i++)
//	{
//		printf("%c ", ps->a[i]);//Q Q Q Q Q Q Q Q Q Q A A A A A A A A A A
//	}
//	//柔性数组成员⽤malloc()函数进⾏内存的动态分配后获得空间，对结构体进行计算大小，还是不会算进去，只算除了柔性数组外的大小
//	printf("%d\n", sizeof(struct S));//4
//	//释放
//	free(ps);
//	ps = NULL;
//
//	return 0;
//}

//柔性数组的优势
//代码一：
struct S
{
	int i;
	char arr[];
};
int main()
{
	int i = 0;
	struct S* p = (struct S*)malloc(sizeof(struct S) + 100 * sizeof(char));
	//业务处理 
	p->i = 100;
	for (i = 0; i < 100; i++)
	{
		p->arr[i] = i;
	}
	free(p);
	p = NULL;
	
	return 0;
}
//1.malloc一次      2.free一次      3.空间连续

//代码二：
struct S
{
	int i;
	char* arr;
};
int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->i = 100;
	ps->arr = (char*)malloc(sizeof(char) * 10);
	if (ps->arr == NULL)
	{
		perror("malloc->arr");
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = 'Q';
	}
	//释放
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;

	return 0;
}
//1.malloc两次      2.free两次      3.内存空间不连续（内存碎片多，浪费空间） 
